# -*- coding: utf-8 -*-

#   This file is part of emesene.
#
#    Emesene is free software; you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation; either version 2 of the License, or
#    (at your option) any later version.
#
#    emesene is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.
#
#    You should have received a copy of the GNU General Public License
#    along with emesene; if not, write to the Free Software
#    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA

''' LaTeX

This plugin will replace any string between a pair of $$ with the LaTeX
rendering of that string.

For example, $$\LaTeX$$ will get converted to the LaTeX icon, and
$$ax^2+bx+c=0$$ will get converted to the general second-degree polynomial.

It will do this both in the user's own sent messages, and also in received
messages from his buddies (autoreplying the received message with the proper
LaTeX replacements).

Acknowledgments: some code based on Dainaccio's emeseLatex plugin.

Version History:

Version 0.1 -- 26 Mar 2009
- Initial release.

Version 0.2 -- 28 Mar 2009
- Minor corrections.

Version 0.3 -- 11 Apr 2009
- Added simple config window with options for temp dir and size

Version 0.4 -- 13 Apr 2009
- Added the feature to let your buddies use the plugin regardless of which
  client they're using. This works by intercepting all incoming messages and
  looking for $$ pairs. If found, your computer will render the LaTeX bits
  and your client will send back the result in the chat.
- Also added a config option to toggle this feature.

Implementation details:

Instead of relying on a latex distribution and dvipng, this plugin uses the
service provided by http://www.sitmo.com/latex/. In my opinion this makes it
simpler, albeit it does depend on an external service over which the user has
no control. The only string processing that is required is replacing some
characters, such as equal signs, in the URL to be sent (the service uses PHP,
so naturally some characters are invalid; sitmo solves this by using special
escape sequences - getting them all right will require testing).
'''

VERSION = '0.4'

import os
import gtk
import urllib2
import tempfile

import Plugin

class MainClass(Plugin.Plugin):

    description = _('Converts any string between a pair of $$ to the LaTeX'
    ' rendering of that string. Example usage: $$e^{i \pi}+1=0$$.')
    authors = { 'Meithan West' : 'meithan@hotmail.com' }
    website = 'None'
    displayName = _('LaTeX Support')
    name = 'LaTeX'

    def __init__(self, controller, msn):
        Plugin.Plugin.__init__(self, controller, msn)

        self.description = _('Converts any string between a pair of $$ to the'
        ' LaTeX rendering of that string. Example usage: $$e^{i \pi}+1=0$$.')
        self.authors = { 'Meithan West' : 'meithan@hotmail.com' }
        self.website = 'None'
        self.displayName = _('LaTeX Support')
        self.name = 'LaTeX'

        self.sendID = None  # handler for outgoing msgs signal connection
        self.recvID = None  # handler for incoming msgs signal connection
        self.controller = controller
        self.conversation = None

        self.config = self.controller.config
        self.config.readPluginConfig(self.name)

        defDir = self.putSlash(tempfile.gettempdir())
        defSize = '1.0'
        defBuddyUse = True
        self.tmpDir = self.config.getPluginValue(self.name,\
            'tmpDir', defDir)
        self.size = float(self.config.getPluginValue(self.name,\
            'size', defSize))
        self.buddyUse = (self.config.getPluginValue(self.name,\
            'buddyUse', 'True') == 'True')

        self.enabled = False

    def start(self):
        '''Plugin start'''
        # connect to the send-message signal to monitor outgoing messages
        self.sendID = self.controller.conversationManager.connect(
            'send-message', self.ownMsg)
        if (self.buddyUse == True):
            # connect to the switchboard::message signal to catch incoming messages
            self.recvID = self.connect('switchboard::message', self.buddyMsg)
        self.enabled = True

    def stop(self):
        self.controller.conversationManager.disconnect(self.sendID)
        if (self.recvID != None): self.controller.msn.disconnect(self.recvID)
        self.enabled = False

    def check(self):
        return (True, 'Ok')

    def ownMsg(self, manager, conversation, message):
        '''Check outgoing message for $$ pairs, send to parser/renderer if found'''
        count = message.count('$$') / 2
        if (count>0):
            # Stop message sending process
            manager.emit_stop_by_name('send-message')
            # Send to parser/renderer
            self.conversation = conversation
            self.sendMsg(message)

    def buddyMsg(self, msn, switchboard, signal, args):
        '''Check incoming message for $$ pairs, send to parser/renderer if found'''
        mail = args[0]
        nick = args[1]
        message = args[2]
        # Check for $$ pairs
        count = message.count('$$') / 2
        if (count>0):
            # Send to parser/renderer
            self.conversation = self.controller.conversationManager.\
                getOpenConversation(mail,switchboard)[1]
            self.sendMsg(message)

    def sendMsg(self, message):
        '''Parse message, render LaTeX fields as emoticons, put message
        back together, send through chat'''
        broken = message.split('$$')
        newmessage = ''
        j = 1
        for i,piece in enumerate(broken):
            # If it's text, just append to newmessage.
            # If it's latex, process it.
            # We can tell by the index of each element in broken:
            #  latex strings have an odd index (if the first is 0),
            #  and the first and last items are never latex
            last = len(broken)-1
            if (i==0 or i==last):
                newmessage += piece
            elif ((i-1)%2==0):
                imagefn = self.render(piece,j)
                if imagefn == 'Error':
                    return
                pieceID = '$latex'+str(j)+'$'
                msnOM = self.conversation.switchboard.msn.getMsnObjectsManager()
                msnObj = msnOM.create(id=pieceID, filename=imagefn, type=2)
                self.conversation.switchboard.sendMessage(msnObj,\
                    contentType='text/x-mms-animemoticon')
                newmessage += pieceID
                j += 1
            else:
                newmessage += piece
        self.conversation.sendMessage(newmessage)

    def render(self, data, j):
        ''' Sends the latex string to sitmo for rendering, retrieves
        the image and saves it to temp dir '''

        # Do required replacements
        eqstr = data
        eqstr = eqstr.replace('%','%25') # Do this one first!
        eqstr = eqstr.replace(' ','%20')
        eqstr = eqstr.replace('+','%2B')
        eqstr = eqstr.replace('/','%2F')
        eqstr = eqstr.replace('=','%3D')
        eqstr = eqstr.replace('&','%26')

        # Build image URL
        imageurl = 'http://www.sitmo.com/gg/latex/latex2png.2.php?z=' +\
            str(int(self.size*100)) + '&eq=' + eqstr

        # Open webpage as an object, fetch image
        webobject = urllib2.build_opener()
        try:
            webpage = webobject.open(imageurl)
        except:
            self.conversation.appendOutputText(\
                None, "LaTeX Support: Couldn't contact site", 'error')
            return 'Error'
        image = webpage.read()

        # Write image to temp file
        filename = self.tmpDir + '$latex' + str(j) + '$' + '.png'
        tempImg = open(filename,'wb')
        tempImg.write(image)
        tempImg.close()

        return filename

    def putSlash(self, path):
        if path[-1] in ('/','\\'): return path
        else:
            if os.name in ('posix','mac'):
                return path + '/'
            elif os.name in ('nt'):
                return path + '\\'

    def configure(self):
        '''Plugin configuration: size, temp dir and buddy usage'''
        optionList = []

        # Temp dir
        tmpDirOpt = Plugin.Option('temp', str, _('Temp '
            'directory:'), _('Set the temp directory'), self.tmpDir)
        optionList.append(tmpDirOpt)

        # Size slider
        sizeAdj = gtk.Adjustment(self.size, 0.5, 2.0, 0.1, 0, 0)
        sizeSlider = gtk.HScale(sizeAdj)
        sizeSlider.set_digits(1)
        sizeOpt = Plugin.Option('size', gtk.Widget, _('Size:'), '',\
            sizeSlider)
        optionList.append(sizeOpt)

        # Toggle buddy usage of the plugin
        buddyUseOpt = Plugin.Option('buddyUse', bool, \
                _('Send back rendered LaTeX in received messages'), '', self.buddyUse)
        optionList.append(buddyUseOpt)

        # Build and open config window, get user response
        configWindow = Plugin.ConfigWindow(_('Plugin Config'), \
            optionList)
        response = configWindow.run()

        if response != None:
            newDir = self.putSlash(response['temp'].value)
            newSize = sizeSlider.get_value()
            newBuddyUse = response['buddyUse'].value

            if os.access(newDir, os.F_OK):
                self.tmpDir = newDir
                self.config.setPluginValue(self.name, "tmpDir", self.tmpDir)

            self.size = newSize
            self.config.setPluginValue(self.name, "size", self.size)

            self.buddyUse = newBuddyUse
            self.config.setPluginValue(self.name, "buddyUse", str(self.buddyUse))
            if (self.buddyUse == True and self.recvID == None):
                self.recvID = self.connect('switchboard::message', self.buddyMsg)
            elif (self.buddyUse == False and self.recvID != None):
                self.controller.msn.disconnect(self.recvID)
                self.recvID = None

        return True